/* 
 * File:   Array.h
 * Author: dativa
 *
 * Created on 2 lipiec 2011, 07:16
 */

#ifndef _ARRAY_H
#define	_ARRAY_H

#include "Comparer.h"
#include "OutOfRangeException.h"

namespace dativa {

    template<typename T> class Array {
    public:
        Array();
        Array(int capacity);
        Array(const Array<T> &array);
        virtual ~Array();

        void add(const T &item);
        void add_array(const Array<T> &array);

        T& pull(int index);
        T get(int index) const;
        void set(int index, const T& item);

		int index_of(const T& item) const;
		int index_of(int index, const T& item) const;
		int last_index_of(const T& item) const;
		int last_index_of(int index, const T& item) const;

        void remove(int index);
        void remove(int from, int to);
        void remove_all(const T& item);
        void clear();

        bool contains(const T& item) const;
        bool empty() const;
        int length() const;
        int size() const;

		int length_bytes() const;
		int size_bytes() const;

        void resize(int capacity);
        void reverse();
        void sort(const Comparer<T>& comparer, bool descending = false);
        void trim();

        void copy_array(Array<T> & array) const;
        bool equals_array(const Array& array) const;

        Array<T> & operator=(const Array<T> &array);
        bool operator==(const Array<T> &array) const;
        T & operator[](int index);

    private:
        T*** items;
        int* count;
        int* capacity;
        int* references;
    };

    template<typename T> Array<T>::Array() {
		count = new int;
		*count = 0;
		capacity = new int;
		*capacity = 0;
		references = new int;
		*references = 1;
		items = new T**;
		*items = new T*[*capacity];
    }

    template<typename T> Array<T>::Array(int capacity) {
        if (capacity < 0) throw OutOfRangeException("Array capacity set to low");
		count = new int;
		*count = 0;
		this->capacity = new int;
		*this->capacity = capacity;
		references = new int;
		*references = 1;
		items = new T**;
		*items = new T*[*this->capacity];
    }

    template<typename T> Array<T>::Array(const Array<T>& array) {
        items = array.items;
        count = array.count;
        capacity = array.capacity;
        references = array.references;
        *references = *references + 1;
    }

    template<typename T> Array<T>::~Array() {
        *references = *references - 1;
        if (*references == 0) {
            for (int i = 0; i < *count; ++i) {
                delete (*items)[i];
            }
            delete[] * items;
            delete items;
            delete count;
            delete capacity;
            delete references;
        }
    }

    template<typename T> void Array<T>::add(const T& item) {
		if (*count == *capacity) {
			*capacity = *capacity == 0 ? 1 : *capacity * 2;
			T** temporary = new T*[*capacity];
			for (int i = 0; i < *count; ++i) {
				temporary[i] = (*items)[i];
			}
			delete[] * items;
			*items = temporary;
		}
		(*items)[*count] = new T(item);
		*count = *count + 1;
    }

    template<typename T> void Array<T>::add_array(const Array<T>& array) {
		if (*count + *array.count > *capacity) {
			*capacity = *count + *array.count;
			T** temporary = new T*[*capacity];
			for (int i = 0; i < *count; ++i) {
				temporary[i] = (*items)[i];
			}
			delete[] * items;
			*items = temporary;
		}
		for (int j = 0; j < *array.count; ++j) {
			(*items)[*count + j] = new T(*((*array.items)[j]));
		}
		*count = *count + *array.count;
    }

    template<typename T> T& Array<T>::pull(int index) {
        if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        return *((*items)[index]);
    }

    template<typename T> T Array<T>::get(int index) const {
        if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        return T(*((*items)[index]));
    }

    template<typename T> void Array<T>::set(int index, const T& item) {
        if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        delete (*items)[index];
		(*items)[index] = new T(item);
    }

    template<typename T> int Array<T>::index_of(const T& item) const {
        for (int i = 0; i < *count; ++i) {
            if (*((*items)[i]) == item) return i;
        }
        return -1;
    }

    template<typename T> int Array<T>::index_of(int index, const T& item) const {
		if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        for (int i = index; i < *count; ++i) {
            if (*((*items)[i]) == item) return i;
        }
        return -1;
    }

    template<typename T> int Array<T>::last_index_of(const T& item) const {
        for (int i = *count - 1; i >= 0; --i) {
            if (*((*items)[i]) == item) return i;
        }
        return -1;
    }

    template<typename T> int Array<T>::last_index_of(int index, const T& item) const {
		if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        for (int i = index; i >= 0; --i) {
            if (*((*items)[i]) == item) return i;
        }
        return -1;
    }

    template<typename T> void Array<T>::remove(int index) {
        if (index < 0 || index >= *count) throw OutOfRangeException("Array index out of range");
        delete (*items)[index];
        int j = 0;
        for (int i = 0; i < *count; ++i) {
            if (i == index) {
                continue;
            } else if (i > index) {
                (*items)[j] = (*items)[i];
            }
            ++j;
        }
        *count = *count - 1;
    }

    template<typename T> void Array<T>::remove(int from, int to) {
        if (from < 0 || from >= *count) throw OutOfRangeException("Array from index out of range");
        if (to < 0 || to > *count) throw OutOfRangeException("Array to index out of range");
        if (from > to) throw OutOfRangeException("Array from index higher than to index");
        int j = 0;
        for (int i = 0; i < *count; ++i) {
            if (i >= from && i < to) {
                delete (*items)[i];
            } else {
                (*items)[j] = (*items)[i];
                ++j;
            }
        }
        *count = j;
    }

    template<typename T> void Array<T>::remove_all(const T& item) {
        int j = 0;
        for (int i = 0; i < *count; ++i) {
            if (*((*items)[i]) == item) {
                delete (*items)[i];
            } else {
                (*items)[j] = (*items)[i];
                ++j;
            }
        }
        *count = j;
    }

    template<typename T> void Array<T>::clear() {
		for (int i = 0; i < *count; ++i) {
			delete (*items)[i];
		}
		delete[] * items;
		*count = 0;
		*capacity = 0;
		*items = new T*[*capacity];
    }

    template<typename T> bool Array<T>::contains(const T& item) const {
        for (int i = 0; i < *count; ++i) {
            if (*((*items)[i]) == item) return true;
        }
        return false;
    }

    template<typename T> bool Array<T>::empty() const {
        return *count == 0;
    }

    template<typename T> int Array<T>::length() const {
        return *count;
    }

    template<typename T> int Array<T>::size() const {
        return *capacity;
    }

    template<typename T> int Array<T>::length_bytes() const {
        return sizeof(this) + *count * sizeof(T);
    }

    template<typename T> int Array<T>::size_bytes() const {
        return sizeof(this) + *capacity * sizeof(T);
    }

    template<typename T> void Array<T>::resize(int capacity) {
        if (capacity == *this->capacity) return;
        if (capacity < *count) throw OutOfRangeException("Array capacity set to low");
        if (capacity < 0) throw OutOfRangeException("Array capacity set to low");
		T **temporary = new T*[capacity];
		for (int i = 0; i < *count; ++i) {
			temporary[i] = (*items)[i];
		}
		delete[] * items;
		*items = temporary;
		*this->capacity = capacity;
    }

    template<typename T> void Array<T>::reverse() {
		T** temporary = new T*[*count];
		for (int i = 0; i < *count; ++i) {
			temporary[*count - i - 1] = (*items)[i];
		}
		delete[] * items;
		*items = temporary;
		*capacity = *count;
    }

    template<typename T> void Array<T>::sort(const Comparer<T>& comparer, bool descending = false) {
        for (int i = 0; i < *count; ++i) {
            for (int j = 0; j < *count; ++j) {
                if (comparer.compare(*((*items)[i]), *((*items)[j])) * (descending ? -1 : 1) < 0) {
                    T* item = (*items)[i];
                    (*items)[i] = (*items)[j];
                    (*items)[j] = item;
                }
            }
        }
    }

    template<typename T> void Array<T>::trim() {
		if (*capacity == 0) return;
		if (*count == *capacity) return;
		T** temporary = new T*[*count];
		for (int i = 0; i < *count; ++i) {
			temporary[i] = (*items)[i];
		}
		delete[] * items;
		*items = temporary;
		*capacity = *count;
    }

    template<typename T> void Array<T>::copy_array(Array<T> &array) const {
        if (this == &array) return;
        *array.references = *array.references - 1;
        if (*array.references == 0) {
            for (int i = 0; i < *array.count; ++i) {
                delete (*array.items)[i];
            }
            delete[] * array.items;
            delete array.items;
            delete array.count;
            delete array.capacity;
            delete array.references;
        }
		array.count = new int;
		*array.count = *count;
		array.capacity = new int;
		*array.capacity = *count;
		array.references = new int;
		*array.references = 1;
		array.items = new T**;
		*array.items = new T*[*array.capacity];
		for (int i = 0; i < *count; ++i) {
			(*array.items)[i] = new T(*((*items)[i]));
		}
    }

    template<typename T> bool Array<T>::equals_array(const Array<T>& array) const {
        return items == array.items;
    }

    template<typename T> Array<T>& Array<T>::operator =(const Array<T>& array) {
        if (this != &array) {
            *references = *references - 1;
            if (*references == 0) {
                for (int i = 0; i < *count; ++i) {
                    delete (*items)[i];
                }
                delete[] * items;
                delete items;
                delete count;
                delete capacity;
                delete references;
            }
            items = array.items;
            count = array.count;
            capacity = array.capacity;
            references = array.references;
            *references = *references + 1;
        }
        return *this;
    }

    template<typename T> bool Array<T>::operator ==(const Array<T>& array) const {
        return equals_array(array);
    }

    template<typename T> T& Array<T>::operator [](int index) {
        return pull(index);
    }

}

#endif	/* _ARRAY_H */
